テーマ
2003 年 2 月のオープンソースプロジェクトの開始以来、Spring Framework はますます強力になっています。ダウンロード件数は 100 万を超え、さまざまな業界のデファクトスタンダードとなり、エンタープライズ Java アプリケーションの開発に変化を与えました。最も重要なことは、大規模かつ忠誠なユーザー基盤を築いたことです。このようなユーザーは Spring Frame の重要な価値を理解し、急速な進歩をもたらすフィードバックを与えてくれました。Spring には、常に以下のような明確な使命がありました。
- 非侵入的なプログラミングモデルの提供。できれば、アプリケーションのコードをフレームワークから切り離すべきである。
- 社内インフラに対する優れたソリューションを提供し、開発担当者が一般的な問題の解決ではなく、ビジネス価値の実現に注力できるようにする。
- 進展しつつあるエンタープライズアプリケーションをできるだけ単純なものにする。ただし、パワーを犠牲にすることなく強化する。
Spring 2.0 は 2006 年 10 月に完成し、上述の価値を一層高めました。2005 年12月フロリダで行なわれる Spring エクスペリエンスコンファレンスを前にして、中心チームは一連のさまざまな開発機能を見据えていたので、シンプルさとパワーという2 つの重要なテーマが Spring 2.0 に共通する流れとして際立っており、初期の Spring のテーマと変わっていないことがわかりました。
簡単に決定できた項目もありました。最初から、Spring 2.0 は完全な下位互換性、または可能な限りほぼ完全な下位互換性を持つことは明らかでした。特に、Spring が多くの企業で事実上の標準(デファクトスタンダード)として地位を確立しているとすれば、ユーザー体験の混乱を一切排除することがきわめて重要です。幸いにも、Spring は非侵入的にするための努力を常に払ってきたため、この目標の達成は可能でした。
10 ヶ月間にわたる開発を経て Spring 2.0 への取り組みが進む中、2005 - 2006 年の Spring の使用方法において明らかになったいくつかの動向を考慮する必要性が生じました。
- Spring は、大規模な組織で使われるケースが多くなっていますが、そのような組織ではプロジェクト別ではなく、戦略的に Spring を利用しています。したがって、下位互換性に関する一定の責任を果たすだけでなく、要求の厳しいユーザーに関連するさまざまな課題に取り組む必要があります。
- Spring を内部で利用しているサードパーティ製の有名なソフトウェア製品が増えているため、最高の設定性やコンテナからの柔軟性が求められる。例を挙げればきりがないので、ごく少数を以下に示します。
- 近々発売される BEA Weblogic Server 1.0。Spring と Pitchfork Project を利用して挿入と傍受を行う。
- BEA WebLogic Real Time (WLRT)。本部での取り引きなどのアプリケーションをターゲットにした BEA の最上位製品であり、待ち時間[GLOVA1]を必要とする。
- Mule、ServiceMix、Apache JetSpeed ポータルコンテナなど、広く利用されているさまざまなオープンソース製品
- 企業を対象としたソフトウェアメーカーが、GigaSpaces、Terracotta、Tangosol といった自社製品に Spring を組み込んでいる。特に、グリッドスペースのソフトウェアメーカーが、プログラミングモデルとして Spring を採用するケースが増えている。
- Oracle の SCA 製品およびその他のさまざまな Oracle 製品。
ビジネスアプリケーションの開発者にとって Spring がさらに良い製品になったため、このような要求の厳しいユーザーのニーズに応える努力をする必要がありました。
35,000 フィートから
Spring 2.0 の大きなビジョンとは?
Spring 2.0 によってさまざまな点が増強されるが、その中で最もわかりやすいものはおそらく以下のポイントでしょう。
- 構成の拡張: Spring 2.0 は拡張可能な XML 構成に対応しているため、Spring Bean の定義を生成するための新しい抽出化を実現する特別要素を開発できるようになる。この XML 拡張メカニズムによって新しいタグが提供できるため、多くの共通タスクを簡素化することもできる。
- AOP フレームワークにおける主な強化ポイントによって、パワーと使い勝手の両方が増強される。
- Java 5 のサポートを強化
- Groovy、JRuby、Beanshell といった動的な言語に Spring BEAN を組み込むことができるだけでなく、Dependency Injection、アウトオブボックス宣言サービス、APO など、Spring コンポーネントモデルのすべてのサービスを維持できる。
- Portlet MVC フレームワーク、「メッセージ駆動型 POJO」、JPA (Java Persistence API) や非同期タスク実行フレームワークなどの新しい API との統合をはじめとする数多くの新機能。
表面に現れない機能も数多く備わっています。これらはわかりにくいですが、重要な役割を果たしています。
- IoC コンテナ拡張ポイントの強化。これによって、フレームワークや製品を Spring の上に簡単に構築できるようになる。
- Spring 特有の統合テストサポートの改良
- AspectJ と Spring の両方を利用して、トランザクション管理や Dependency Injection といった Spring の中核機能を実現する AspectJ アスペクトをユーザーに提供。
重要な点は、このような機能が、全体的に調和して連携的に機能するように設計されているということです。
概要
この記事は 2 つのパートに分かれています。パート 1(この記事)では、コアコンテナ、XML 構成拡張、AOP の強化、Java 5 に特有の機能について記述します。
パート 2(来月以降に投稿)では、メッセージング、動的な言語のサポート、JPA、Web 層の強化を扱います。また、表面に出てこない数多くの改良点についても取り上げます。
それでは、コード例を示しながら、新機能のいくつかをさらに詳しく見てみましょう。
XML 構成の拡張
Spring 2.0 における最もわかりやすい強化点の 1 つは、XML 構成に関するものです。
Spring IoC コンテナは、実際には XML などのメタデータの表現には依存していません。Spring には、Java オブジェクト(BeanDefinition とサブインターフェイス)という形で独自の内部メタデータが組み込まれています。コメントを利用する Java 構成など、XML 構成を補足する代替品に関しては、活発な研究が行なわれています (http://blog.interface21.com/main/2006/11/28/a-java-configuration-option-for-spring/)。
しかし、XML は現在 Spring の構成に使われるケースが最も多く、Spring のコアの構成改善における最大のポイントです。
Spring 2.0 における XML の強化は、シンプルとパワーという 2 つのテーマを的確に表すものです。つまり、XML の強化によって一部の共通タスクの実行が簡素化されるだけでなく、ほかの高度なタスクも可能になっているのです。
目標
これまで、Spring の XML 構成シンタックスと Spring の内部メタデータの間には、1 対 1 の関係がありました。1 つの要素によって、1 つの BeanDefnition が生成されていたのです。
これは私たちが一般的に望むことであり、フレームワークが認識していないアプリケーションのクラスを構成する上で理想的でもあります。
しかし、繰り返し使われることが多い特定のクラス、たとえば、JNDI からオブジェクトを探し出して、挿入可能なオブジェクトとして Spring のコンテナに入れるのに使われる JndiObjectFactory のようなジェネリッククラスのことをフレームワークが知っていなければならないとしたらどうでしょう。複数の Bean 定義を同時に使用しなければ意味がないとしたらどうでしょう。
したがって、新しい形の抽象化によって、重要なメリットがもたらされます。
以下の JNDI のルックアップの例について考えてみましょう。
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/jpetsore" />
</bean>
これは、サービスロケーターを実装していたひどい時代よりは確かによいですが、完全なものではありません。われわれは常に同じ Bean クラスを使おうとしています。そして(少なくとも、Java 5 を使わないのであれば)、jndiName プロパティが必要ですが、ほかのプロパティは必須でないことを示すメカニズムがありません。
以下のように、Spring 2.0 ではボックスからネーム空間 jee および同じ JNDI ルックアップを実行できるタグが加えられます。
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>
これはよりシンプルで明確であり、意図を明確に表現しています。また、より簡潔でもあります。そして、同じスキーマによって、jndiName プロパティが必須であることをとらえ、ツールのサポートを容易にしています。以下の例が示すように、そのほかのオプションのプロパティもこのスキーマで表現されています。
<jee:jndi-lookup id="simple"
jndi-name="jdbc/MyDataSource"
cache="true"
resource-ref="true"
lookup-on-startup="false"
expected-type="com.myapp.DefaultFoo"
proxy-interface="com.myapp.Foo"/>
このような比較的に単純なケースでは、属性名は構成中のクラスのプロパティ名とほとんど同じですが、これは必要でないことに注目してください。属性とプロパティの間にネーミングの対応、つまり 1 対 1 の対応性は必要ありません。また、サブ要素の内容も処理することができます。そして、すでに述べたように、拡張タグから任意の数の Bean 定義を生成することができます。
それでは、カスタムタグの能力を利用して、複数の Bean 定義を生成するようなもっと複雑な例を見てみましょう。
Spring 1.2 以来、@Transactional コメントを Spring に認識させ、プロキシ処理することによって、関係する Bean を自動的に処理可能にすることができるようになりました。その結果、シンプルな展開モデルができました。コメントが付けられた Bean を加えるだけで、自動的に処理可能になります。しかし、それを設定するにはいくらか複雑なマジックが必要でした。必要な共同オブジェクトには、3 つの Bean 定義が必要とされました。3 つの Bean 定義とは、Spring AOP Advisor、TransactionInterceptor、自動プロキシを引き起こすDefaultAdvisorAutoProxyCreator です。このようなマジックの Bean 定義が呪文となり、さまざまなアプリケーションでそのまま利用できましたが、ユーザーが知る必要のない細かい部分が露呈することとなりました。
<bean
class="org.springframework...DefaultAdvisorAutoProxyCreator"/>
<bean class="org.springframework...TransactionAttributeSourceAdvisor">
<property name="transactionInterceptor
ref="transactionInterceptor"/>
</bean>
<bean id="transactionInterceptor"
class="org.springframework...TransactionInterceptor">
<property name="transactionManager"
ref="transactionManager"/>
<property name="transactionAttributeSource">
<bean
class="org.springframework...AnnotationsTransactionAttributeSource">
</bean>
</property>
</bean>
この抽象化のレベルは間違ったものです。ユーザーは、トランザクションを管理する上で、Spring AOP フレームワークの仕組みに関してここまで知る必要はなく、その意図も不明確です。Spring 2.0 では、新しい tx ネーム空間で単一のタグが提供されるため、これらすべての定義が以下のように置き換えられます。
<tx:annotation-driven />
このアウトオブボックスタグによって同じ結果が得られます。このタグは意図を明確に表現しており、トランザクションのコメントを自動的に認識します。それでも、3 つの Bean 定義は、表面に出てこない拡張タグによって生成されますが、それは現在、(正しくは)ユーザーでなくフレームワークが扱う問題です。
Spring 2.0 の拡張タグは、必要に応じてタグ自体の属性やサブ要素の構造を定義できます。ネーム空間を定義する開発担当者は完全に把握しています。Spring 2.0 の拡張タグを処理する NamespaceHandler は、拡張タグから Bean 定義をいくつでも作成できます。
拡張タグを使うためには、DTD ではなく XML スキーマを使い、該当するネーム空間をインポートする必要があります。デフォルトのネーム空間は Bean スキーマである必要があります。以下に示す tx ネーム空間の例では、
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
拡張タグは通常の Bean 定義と組み合わせることができ、任意の数のネーム空間をインポートして同じドキュメントで使用できます。
アウトオブボックスネーム空間
Spring 2.0 では、複数のアウトオブボックスネームス空間が使用できる。最も重要なポイントを以下に示します。
- トランザクション管理(”tx”): これまで見てきたように、Spring 2.0 では、Spring Bean を処理可能にすることがきわめて簡単になりました。また、処理動作をメソッドにマッピングする「トランザクション属性」の定義も簡単になる。
- AOP ("aop"): Spring 2.0 では、特殊タグによって AOP 構成が以前よりも格段に簡潔になり、IoC コンテナが AOPフレームワーク に依存する必要もなくなる。
- Java EE ("jee"): これまで見てきたように、JNDI などの Java EE API の処理が簡単になる。それでも、EJB ルックアップ は JNDI ルックアップ よりも増大する。
- 動的な言語("lang"):動的な言語における Bean の定義が簡単になる。Spring 2.0 の新機能。
- Utils ("util"): java.util.Properties オブジェクトなど共通タスクのロードが簡単になる。
Spring 2.1 以降では、さらに多くのネーム空間を加えて、このシンプルさを新しい領域でも実現します。コアに最初に追加されるのは、おそらく Spring MVC と JPA の使い方を簡単にするネーム空間です。
サードパーティの構成拡張
拡張メカニズムとして、Spring 2.0 のネーム空間に関連する最も重要な可能性は、Spring のコアの外部にあるでしょう。
ネーム空間を使うと、Spring を基盤とした多くの製品とその構成を簡素化できます。その好例は、Acegi Security for Spring(2007 年の初めに Spring Security というブランドに改称)です。Acegi Security for Spring では、複数の共同 Bean の定義を設定する必要があります。Spring 2.0 のネーム空間はこの定義を格段に簡素化し、もう一度言いますが、意図をより明確に表現します。
Spring と密接に融合している製品は数多くありますが、そのような製品のメリットは明らかです。Tangosol のCoherence の統合はその好例です。
その他の可能性がある例としては、IBM の ObjectGrid など、Spring フレンドリーな構成を採用した製品が挙げられます。現時点では、ObjectGrid は内部で Spring を使っていませんが、JavaBeans で構成することによって、Spring をベースとしたアプリケーションと簡単に融合できるようになっています。拡張スキーマによって、この融合は格段に簡単になります。
XML ドキュメントでは、拡張タグをトップレベル要素として使用できます。したがって、拡張スキーマ要素の前にネーム空間を付ける必要がなくなり、Spring を中心とした構成ではなく、「ネイティブ」な構成に見せることができるようになります(通常、要素はデフォルトのネーム空間にあるため、従来の Spring の Bean 定義には接頭辞を付ける必要がありません)。
時が経つにつれて、JSP 特殊タグと同様、経験から確実な汎用タグを志向するようになるでしょう。ユーザーが、ネーム空間のライブラリを作成してコミュニティにメリットを与えるようになると予想しています。
XML 拡張の実装
ネーム空間の実装は比較的簡単であり、以下の3 つの手順で行います。
- XML スキーマを定義する。この手順が最も難しく、適切なツール設定が必要である。スキーマには一切制限がないが、スキーマによって、実行時に BeanDefinition メタデータの生成がどのように導かれるのかを理解する必要がある。
- NamespaceHandler インターフェイスを実装して、スキーマの要素や属性から BeanDefinition を生成する。
- 特別登録ファイル spring.handlers を編集して、新しい NamespaceHandler クラスが Spring にわかるようにする。
Spring に付属する spring.handlers ファイルには、「標準」ネーム空間がどのように構成されているかが記述されています。:
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
さまざまな /META-INF ディレクトリに複数の spring.handlers ファイルを配置することができ、Spring はそれらのファイルを実行時にマージします。
上記の手順を実行すると、新しい拡張を使用することができます。
NameSpaceHandler インターフェイスの実装は難しくありません。W3C DOM 要素を使って、それを処理することで BeanDefinition メタデータを生成します。Spring が XML の構文解析をするので、作成したコードはツリーをたどるだけでよいのです。
public interface NamespaceHandler {
void init();
BeanDefinition parse(Element element, ParserContext parserContext);
BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);
}
parse() メソッドは最も重要であり、与えられたコンテキストに BeanDefinition を加える役割を果たします。
decorate() メソッドにも注目してください。NamespaceHandler は、スコープ付きのプロキシを作成する以下のシンタックスのように、囲み Bean 定義を装飾することもできます。
<bean id="scopedList" class="java.util.ArrayList" scope="request">
<aop:scoped-proxy/>
</bean>
BeanDefinition メタデータの生成を簡素化するために、Spring 2.0 では便利な新しい BeanDefinitionBuilder クラスが導入され、滑らかなビルダースタイルの API が実現しています。NamespaceHandlers の実装を始めるのに最善の手引きは、Spring コアに同梱されているものです。UtilNamespaceHandler は比較的簡単な例であり、AopNamespaceHandler は、複雑なサブ要素構造の構文解析を行う高度な例です。
ベストプラクティス: 独自のネーム空間はいつ定義すべきでしょうか。
かなづちを持っているという理由だけで、すべてのものが釘だとは限りません。これまで見てきたように、Spring 2.0 の拡張メカニズムはさまざまなケースで大きな価値をもたらしますが、十分な理由がないのであれば使うべきではありません。XML 拡張タグは新しい抽象化であるため、新しく覚えなければならないこともあります。Spring の正規の XML フォーマットはすでに数万人の開発担当者になじみの深いものであり、Spring を初めて扱う人にとっても直感的にわかるものです。Spring の XML ファイルは、アプリケーション構造をわかりやすく示すマップまたは設計図です。コンフィギュレーションで不慣れなカスタムタグを多数使っている場合は、必ずしもこうなるとは限りません。
この分野における関連する経験について考えてみましょう。JSP のカスタムタグが好例です。最終的に、JSTL、Struts、Spring MVC タグライブラリといった、経験から生まれたうまく設計されたタグライブラリという形で、このタグは真のメリットを生み出しました。しかし、初期段階では、JSP を混乱させる可能性すらあるため、非常に嫌われていました(1 つか 2 つ自分自身で実装していたため、ここでは経験に基づいて話しています)。
ネーム空間ハンドラは、新しい重要な拡張ポイントであり、そして Spring の価値がある新しい抽象化であると考えてください。これは、Spring の上にサードパーティ製の製品を構築する人たちにとってすばらしいことであり、非常に大規模なプロジェクトに役立ちます。間もなく、これらがない生活を想像するのが難しい時代になります。しかし、エンドユーザーは、これらを消費することよりも実装することに慎重になったほうがよいでしょう。
もちろん、aop、tx、jee など、ボックスから Spring に提供される拡張タグは、近いうちに要素として広く知られているSpring 構成語彙の中心的な部分になるでしょう。もっと冗長な方法で同じことを達成する従来の方法よりも、このようなタグを使うべきであることは確かです。
シンタックスシュガー
スキーマに移行することによって、プロパティ値のサブ要素ではなく属性を使って、多少近道をすることができます。このような属性の妥当性は確認されていませんが、私たちは DTD ではなく XML スキーマを使おうとしているので、そのほかのすべての妥当性確認を今でも保持することができます。属性名はプロパティ名なので、いずれにせよ XML 妥当性確認によって付け加えられるものは何もありません。これは、XML 構造ではなく、Java をベースとした妥当性確認の問題です。
単純な 2 つのプロパティを持ち、関連オブジェクトに依存する以下の Java オブジェクトについて考えてみましょう。
public class Person {
private int age;
private String name;
private House house;
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setHouse(House house) {
this.house = house;
}
}
これは、XML を使って以下のように構成できます。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.interface21.spring2.ioc.Person"
p:name="Tony"
p:age="53"
p:house-ref="number10"
/>
<bean class="com.interface21.spring2.ioc.House"
id="number10"
p:name="10 Downing Street"
/>
</beans>
要素ではなく属性を使って、どのようにプロパティが提供されるのかに注目してください。これは、特殊なネーム空間 p のマジックによって機能します。このネーム空間の妥当性は確認されていませんが、Java のプロパティ名と名前が一致する属性を使用することができます。
単純なタイプで、p:name のように、名前空間 p のプロパティ名を使うだけです。そのほかの Spring Bean への参照を挿入する場合は、p:house-ref のように、接尾辞 ?ref を使います。
このショートカットシンタックスは、オートワイヤリングを使いたいときに、特に強制的に使う必要があります。たとえば、以下のような可変部について考えてみましょう。
<bean class="com.interface21.spring2.ioc.Person"
autowire="byType"
p:name="Tony"
p:age="53"
/>
ここでは、house プロパティを設定していません。オートワイヤリングが設定できるからです。また、
Spring 1.0 または 1.1 の使い方の以下の断片を見ると、Spring の構成によって、最近の 2 つの主要なリリース(1.2 と 2.0)における山括弧(<、>)の最少数が大幅に少なくなったことがわかります。
<bean class="com.interface21.spring2.ioc.Person">
<property name="name"><value>"Tony"</value></property>
<property name="age"><value>"53"</value></property>
<property name="house"><ref local="number10" /></property>
</bean>
Spring 1.2 では、ほとんどのケースでサブ要素を要求する代わりに、value 属性と ref 属性を導入しましたが、Spring 2.0 では、pure 属性と simple 属性が使えます。
もちろん、従来の XML フォームは今後も機能する。プロパティ値が複雑で、属性値として非正規または判読不可能な場合に従来の XML フォームを使います。もちろん、既存の構成ファイルを書き換える必要ありません。.
Spring IoC コンテナには、XML 構成拡張以外にも、数多くの新機能が備わっています。
IoC コンテナのその他の強化点
新しい Bean スコープ
XML の拡張とともに、IoC コンテナの最も重要な特徴は、Bean のライフサイクル管理をするためのカスタムスコープを追加したことです。
1. 背景
従来から Spring には、SingletonとPrototype (つまり、非 Singleton)という 2 つの Beans 用スコープが備わっていました。
Singleton Bean は、所有するコンテナのコンテキストにある単集合です。コンテナの存続中にちょうど 1 つのインスタンスが存在し、コンテナが閉じられると、破壊イベントに関係しているすべての Singleton Bean にイベントを発行し、たとえば、プールされた接続のような管理されたリソースを閉鎖します。
Prototype Bean は、別の Bean への挿入を通じて参照される場合や、所有するコンテナに対する getBean() 呼び出しに対応して参照される場合に必ず作成されます。この場合、Bean 定義は 1 つのオブジェクトに対応しているのではなく、オブジェクトを作成するための方法です。作成された各インスタンスはまったく同じように構成されますが、別個のアイデンティティを持ちます。Spring のコンテナは、Prototype への参照に依存しません。そのライフサイクルは、それを取得したコードが責任を負います。
Spring 2.0 では、カスタムスコープの機能を搭載しました。カスタムスコープには任意の名前を付けることができます。このスコープは一般的に、オブジェクトのインスタンスを管理できる補助記憶に対応しています。この場合、Spring はよく知られているプログラミングモデルを提供するので、挿入と参照が可能になり、スコープされたオブジェクトのインスタンス管理を補助記憶が行います。
一般的な補助記憶は以下のとおりです。
- HTTP セッション
- クラスタードキャッシュ
- その他の永続的な記憶
2. Web スコープ
この機能に対する最も一般的な要件は、Web アプリケーションの HTTP セッションにオブジェクトをトランスペアレントに保存することに関するものです。これは、Spring 2.0 でサポートされたアウトオブボックスです。この要件は一般的であるため、例としては優れた基盤です。
以下の Bean 定義について考えてみましょう。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
ここでは、デフォルトの singleton ではなく、session スコープを指定します。スコープには任意の名前を付けられますが、Web アプリケーションで使うために、session と request がボックスから提供されます。
userPreferences の名前でgetBean() を呼び出すと、Spring は 現在の HTTP セッションからUserPreferences オブジェクトを透過的に解決します。UserPreferences オブジェクトが見つからない場合は、Spring によって作成されます。挿入によって、再設定した UserPreferences の設計図が可能になり、当該ユーザー用にカスタマイズできるようになります。
これを有効にするためには、web.xml ファイルで以下のようにフィルタを定義する必要があります。そのためには、Spring がどの HTTP セッションのオブジェクトを参照すべきなのかがわかるように、見えないところでバインドする ThreadLocal が処理されます。
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener></listener-class>
</listener>
...
</web-app>
この場合は、ルックアップに対処しているが、通常は API を使わない挿入スタイルのほうが好ましいでしょう。"userPreferences" ライフサイクルがより長い Bean をほかの Bean に挿入したい場合はどうなるでしょうか。たとえば、以下のような Spring MVC Controller 単集合の個々の UserPreferences オブジェクトを処理したい場合はどうでしょう。
public class UserController extends AbstractController {
private UserPreferences userPreferences;
public void setUserPreferences(UserPreferences userPreferences) { this.userPreferences = userPreferences;
}
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
// do work with this.userPreferences
// Will relate to current user }
}
この場合は、より短命の UserPreferences オブジェクトが挿入対象の使用ポイントで解決されるような効果的な「ジャストインタイム」の挿入が必要になります。
一般的に、Spring の挿入は静的であるため必ずステートレスになると誤解されていますが、これは間違いです。Spring には、高度なプロキシベースの AOP フレームワークが備わっているので、実行時にルックアップを隠して、このような「ジャストインタイム」機能を実現しています。IoC コンテナは挿入されるものを制御するため、必要な lookup を隠すプロキシを挿入することができます。
以下のように、コンテナにこのようなプロキシを実行するように簡単に指示できます。サブ要素
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userController" class="com.mycompany.web.UserController">
<!-- a reference to the proxied 'userPreferences' bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
これで、予想どおり解決は動的に行なわれます。UserController の userPreferences インスタンス変数は、HTTP セッションの正しい対象 UserPreferences を解決するプロキシになります。HTTP セッションと直接対話する必要がなく、模倣の HttpSession オブジェクトを使わずに簡単に UserController をユニットテストできます。
「ジャストインタイム」挿入を実現するもう 1 つの方法は、lookup メソッドを使う方法です。これは、Spring 1.1 以降で可能になった手法であり、この手法によって寿命の長い(通常は単集合)Bean を、より短命の可能性がある Bean に依存させることができます。コンテナは抽象化(または具象化)メソッドを無効にして、メソッドの呼び出し時に getBean() 呼び出しの実行結果を返すことができます。
この場合は、挿入対象のインスタンス変数ではなく、必要なオブジェクトを返す抽象化メソッドを定義します。このメソッドは、公開または保護されている可能性があります。
public abstract class UserController extends AbstractController {
protected abstract UserPreferences getUserPreferences();
@Override protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
// do work with object returned by getUserPreferences()
// Will relate to current user }
}
この場合は、スコープされたプロキシを使う必要はありません。挿入された Bean ではなく、挿入対象の Bean 定義を変更する。XML 構成は以下のようになります。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session" />
<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userController" class="com.mycompany.web.UserController">
<!-- a reference to the proxied 'userPreferences' bean -->
<lookup-method name="getUserPreferences" bean="userPreferences" />
</bean>
</beans>
このメカニズムでは、クラスパス上に CGLIB が必要になります。
3. その他の可能性
当然ながら、Spring 本来の手法では、基盤となるメカニズムはプラグ可能であり、Web 層と連動していません。たとえば、Tangosol と Interface21 が提供する datagrid ネーム空間を利用して、Tangosol Coherence のスコープを以下のように使うことができます。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:datagrid="http://schemas.tangosol.com/schema/datagrid-for-spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://schemas.tangosol.com/schema/datagrid-for-spring
http://schemas.tangosol.com/schema/datagrid-for-spring/datagrid-for-spring.xsd">
<datagrid:member/>
<bean id="brian" class="Person" scope="datagrid">
<aop:scoped-proxy/>
<property name="firstName" value="brian" />
<property name="lastName" value="oliver" />
<property name="age" value="21" />
</bean>
</beans>
Bean が datagrid 内でスコープされていることを宣言するということは、Bean の状態管理は Coherence によって、ローカルサーバー上ではなくクラスタードキャッシュで行なわれるということです。もちろん、Bean は Spring によってインスタンス化されて挿入され、Spring のサービスの恩恵を受けることができます。スコープされた Bean は、SOA やバッチプロセスのコンテキストで役に立つことを期待しており、将来の Spring のリリースにおいて、アウトオブボックスのスコープをさらに増大させると考えています。
4. カスタムスコープ
ユーザー独自のカスタムスコープは簡単に定義できます。その方法については、リファレンスマニュアルに詳細に記載されています(http://static.springframework.org/spring/docs/2.0.x/reference/beans.html#beans-factory-scopes)。現在のスコープのオブジェクトを解決する方法を明確にする戦略が必要です。Web スコープは見えないところでこれを実行するため、一般的には ThreadLocal が必要になります。
タイプインターフェイス[GLOVA2]
Java 5 を実行している場合、Spring 2.0 ではジェネリクスなどの新しい機能の恩恵を受けることができます。たとえば、以下のようなクラスがあるとします。
public class DependsOnLists {
private List plainList;
private ListfloatList;
public ListgetFloatList() {
return floatList;
}
public void setFloatList(ListfloatList) {
this.floatList = floatList;
}
public List getPlainList() {
return plainList;
}
public void setPlainList(List plainList) {
this.plainList = plainList;
}
}
plainList プロパティは旧式のコレクションですが、floatList プロパティには型付き表示があります。
以下の Spring 構成について考えてみましょう。
<bean class="com.interface21.spring2.ioc.DependsOnLists">
<property name="plainList">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
<property name="floatList">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
</bean>
Spring は floatList プロパティに、文字列ではなくフロートを正しく挿入します。型変換を行う必要があることを理解するのに十分スマートな方法だからです。
以下のテストによって例証します。
public class GenericListTest extends
AbstractDependencyInjectionSpringContextTests {
private DependsOnLists dependsOnLists;
public void setDependsOnLists(DependsOnLists dependsOnLists) {
this.dependsOnLists = dependsOnLists;
}
@Override
protected String[] getConfigLocations() {
return new String[] { "/com/interface21/spring2/ioc/inference.xml" };
}
public void testLists() {
List plainList = dependsOnLists.getPlainList();
ListfloatList = dependsOnLists.getFloatList();
for (Object o : plainList) {
assertTrue(o instanceof String);
System.out.println("Value='" + o + "', class=" +
o.getClass().getName());
}
for (Float f : floatList) {
System.out.println("Value='" + f + "', class=" +
f.getClass().getName());
}
}
}
出力は以下のようになります。
Value='1', class=java.lang.String
Value='2', class=java.lang.String
Value='3', class=java.lang.String
Value='1.0', class=java.lang.Float
Value='2.0', class=java.lang.Float
Value='3.0', class=java.lang.Float
新しい拡張ポイント
IoC コンテナには数多くの拡張ポイントがあります。具体的には、以下のようなポイントです。
- PostPostProcessor フックの追加。これによって、Pitchfork のようなプロジェクトにさらに大きなパワーを与え、カスタムコメントを処理したり、インストール時や Spring Bean の構成時の任意の時点で、その他の操作を行ったりすることができる。
- BeanDefinition メタデータに任意のメタデータを追加する機能。Spring 自体には意味がないが、Spring 上に構築したフレームワークやクラスタリング製品など、Spring と統合した製品が処理できるような情報を追加する際に便利である。
これらのトピックは主にパワーユーザーや Spring を使って製品を記述する人たちに関連するものであるため、この記事で取り扱う内容ではありません。しかし、Spring 2.0 の強化点は表面的なことばかりではなく、目に見えない部分で多くの努力が行なわれてきたということを理解しておくことが重要です。
OSGi との統合をサポートするためのさまざまな強化が行なわれてきたため、Spring コンポーネントモデルを利用して動的なモジュール管理のパワーと OSGi を統合する Spring OSGi 統合プロジェクトが可能になりました。この作業は、Spring JAR ファイルを OSGi バンドルとしてパッケージングすることで、Spring 2.1 でも続けられます。
AOP の強化
Spring 2.0 における最も刺激的な強化点の 1 つは Spring AOP に関するものですが、Spring AOP はより使いやすく、そしてよりパワフルになります。これは主に、純粋な Java プロキシベースのランタイムを維持しながら高度で成熟した AspectJ 言語の機能性を活用する能力によって実現するものです。
AOP (Aspect Oriented Programming: アスペクト指向プログラミング)は重要であると私たちはずっと信じてきました。その理由は、AOP はプログラム構造に関する新しい考え方を示してくれるものであり、それによって純粋な OOP では解決できなかった多くの重要な問題を解決でき、分散した方法ではなくモジュラーに特定の要件を実装できるからです。
これらのメリットを理解するために、純粋な OO コードに直接実装できないが要件で表現できることについて考えてみましょう。企業の開発担当者は、明確に伝達できる共通語彙を使います。たとえば、サービス層、DAO 層、Web 層、Web コントローラーといった用語は説明する必要がありません。
多くの要件がこのような語彙の用語で表現されます。たとえば、
- サービス層は処理可能である必要がある。
- DAO 操作によって SQLException などの永続テクノロジーに対応できない場合は、特定の例外を変換して、DAO インターフェイスが漏れやすい抽象化を行わないようにする必要がある。
- サービス層のオブジェクトは Web 層を呼び出すべきではない。各層は直下の層のみに依存すべきだからである。
- 同時処理に関連する失敗とともに失敗する等羃のビジネスサービスを再試行できる。
これらの要件すべては現実的で経験から引き出されたものですが、純粋な OOP を使って簡潔にアドレス指定することはできません。主な理由として以下の 2 つが挙げられます。
- 語彙のこれらの用語は、意味はわかりますが抽象化ではありません。用語を使ってプログラミングすることはできません。抽象化が必要です。
- すべてが、クロスカッティング問題と呼ばれるものの例です。クロスカッティング問題を従来の OO アプローチに実装すると、さまざまなクラスやメソッドを横切ります。たとえば、DAO 層で特定の例外が発生した場合の再試行ロジックの適用について考えてみるとよいでしょう。この問題はさまざまな DAO メソッドを横切っており、従来の方法で実装するには、さまざまな修正を別個に行う必要があります。
AOP は、クロスカッティング問題をモジュール化し、プログラム対象となる抽象化として、共通語彙の用語を表現できるようにすることによって、このような問題を解決するテクノロジーです。このような抽象化はポイントカットと呼ばれているが、これについてもう少し詳しく手短に説明します。この方法の主なメリットは以下のとおりです。
- カット & ペーストによるコピーをなくしたことによってコードの行数が減少。これは、例外変換や監視の実行などの try/catch/finally イディオムにおいて特に大きなメリットがある。
- このような要件を単一コードモジュールに取り込める機能によって、追跡可能性が向上する。
- このような機能のバグを、アプリケーションのさまざまな箇所に再度行かなくても、一箇所で修正できる機能。
- クロスカッティング問題によって核となるビジネスロジックが不明瞭にならないようにする。開発が進む中でさまざまな問題としての本当の危険が累積される。
- 開発担当者とチームの間の責任分離が明確になる。たとえば、多くの開発担当者が複数のサブシステムのコーディングをしなくても、ひとりの開発担当者またはチームで再試行機能をコード化することができる。
このような理由で AOP は重要であり、最高のソリューションを提供したいのです。
Spring AOP は間違いなく最も広く利用されている AOP テクノロジーです。このポジションを得られたのは、以下のような強みがあるからです。
- 採用コストがほぼゼロである。
- 真のポイントカットを実現しているので、単なる傍受ではなく、AOP ということばにふさわしい。
- プログラム的にも XML 経由でも、さまざまな方法での利用に対応した柔軟性のあるフレームワークを実現。
しかし、Spring 2.0 より前は、Spring の AOP には以下のようないくつかの欠点がありました。
- Java コードを書かずに表現できるのは簡単なポイントカットだけであった。RegexpMethodPointcutAdvisor で、正規表現をベースにした簡単なポイントカットを定義することはできたが、高度なポイントカットを文字列中で簡潔に表現できるポイントカット表現言語がなかった。
- 複雑な AOP 使用シナリオを構成する際、XML 構成が複雑になることがあった。総称要素を使って AOP クラスを構成していた。これは、整合性においてはすばらしかったので、DI などのサービスをアスペクトやクラスに提供できたが、専用の構成アプローチほど簡潔ではなかった。
- Spring AOP は、きめ細かいオブジェクトに教えることには適していなかった。オブジェクトは Spring で管理するか、プログラムによってプロキシ処理する必要がある。
- ごく少数のケースで、プロキシを利用したアプローチのパフォーマンスオーバーヘッドが問題になる可能性がある。
- Spring AOP は、プロキシとターゲット(装飾中またはアドバイス中のオブジェクト)を分離するので、ターゲットメソッドがターゲットでメソッドを呼び出した場合は、プロキシが使われない。つまり、AOP アドバイスが適用されないということである。AOP に対してプロキシを利用した方法を使うことには賛否両論あるが、それはこの記事で扱うことではない。絶対的なプラス要素もあるが(同じクラスのさまざまなインスタンスに異なるアドバイスを適用できることなど)、これは大きなマイナスである。
Spring 2.0 のこの重要な分野を強化するために、私たちはその強みを基盤として構築しながら、弱点に対応したいと考えました。
目標
最初の 2 つの弱点は最も重要です。どちらもポイントカットに関連するものです。最後の 3 つは、実際には Spring ユーザーにはあまり発生しないものですが、問題があることが判明した場合は、AspectJ の使用をお勧めします(おわかりのとおり、これは現在、Spring AOP からの単純明快な進歩です)。
XML 構成拡張によって、重要課題の 1 つが解決できました。Spring モジュラーの設計は変えたくなかったので、これまでは Spring DTD の AOP に固有のタグを提供できませんでした。そのため、このようなケースでは冗長になる可能性がある汎用構成に依拠する必要がありました。Spring 2.0 では、この問題は解消されました。DTD とは異なり、XML スキーマは拡張が可能だからです。私たちは、IoC コンテナに AOP 構造物を認識させると思われる AOP ネーム空間を、モジュラー性を損なうことなく提供することができました。
AOP 用語 101: ポイントカットとアドバイスについて
AOP 関連の用語の修正を簡単にしきましょう。Spring AOP を使ったことがある人にとって、これらの概念はなじみ深いものでしょう。概念はまったく同じです。単に、別のそして簡潔でパワフルな表現方法であるいうことだけの違いです。
ポイントカットは突き合わせルールであり、アスペクトを適用すべきプログラムの実行における一連のポイントを定義するものです。このようなポイントは、ジョインポイントと呼ばれます。アプリケーションの実行にともなって、オブジェクトのインスタンシエーションやメソッドの呼び出しなどのジョインポイントが接近します。Spring AOP(すべてのバージョン)の場合、サポートされている唯一のジョインポイントは公開メソッドの実行です。
アドバイスは、アスペクトがジョインポイントに適用する挙動です。アドバイスはジョインポイントの前後に適用できます。アドバイスの全種類は以下のとおりです。
- Before アドバイス: ジョインポイントの前で呼び出されるアドバイス。たとえば、メソッドの呼び出しが行なわれようとしていることをログ記録すること。
- リターン後のアドバイス:ジョインポイントのメソッドが正常に戻される場合に呼び出されるアドバイス。
- AfterThrowing アドバイス(Spring 1.x では ThrowsAdvice と呼ばれる): ジョインポイントのメソッドが特定の例外を投入する場合に呼び出されるアドバイス。
- After アドバイス: 結果に関係なく、ジョインポイントのあとに呼び出されるアドバイス。どちらかというと、Java では最終的にという感じである。
- Around アドバイス: ジョインポイントを呼び出すかどうかを完全に制御できるアドバイス。たとえば、トランザクションにおいてメソッドに呼び出しをラップしたり、メソッドの実行のタイミングを取ったりするときに使う。
アスペクトは、ポイントカットとアドバイスを結合し、特定のクロスカッティング問題に対応するモジュラーソリューションを作成します。
現時点では、少し抽象的に思えたとしても心配する必要はありません。コードの例をみれば、すぐにすべてが明確になるでしょう。
Spring 2.0 および AspectJ のコンテキストにおける AOP の基本に関するもっと詳しい説明については、infoq の Adrian のすばらしい記事「Spring 2.0 と AspectJ で企業アプリケーションを簡素化する」 (source)を参照してください。
AspectJ のポイントカット表現を使う理由
これまで論じてきた概念は、Spring AOP や AspectJ に固有のものではなく、AOP の基本的な概念であり、Spring 1.x ですでにあった概念です。それでは、なぜ私たちは Spring 2.0 で、AspectJ との連係を選んだのでしょうか。
もしポイントカット表現言語が必要なのであれば、選択は簡単です。AspectJ には、よく考えられ、厳格に定義され、うまくドキュメント化されたポイントカット言語が備わっています。最近、総合的な全面改訂を行ない、Java 5 での実行時に Java 5 シンタックスを利用できるようにしました。専用の優れた参考資料はありませんが、これについて説明している書籍や記事は数多くあります。
私たちは、一からやり直す必要はない(無駄な作業の重複は避けるべき)と考えており、独自のポイントカット表現言語を定めることの正当性を認められませんでした。さらに、2005 年の初めの AspectWerkz を AspectJ に統合するプロジェクト以来、AspectJ は Spring 2.0 以外では、唯一の主流 AOP テクノロジーであることは明らかです。したがって、臨界質量が重要ポイントであり、技術的な優秀さでした。
新しい XML シンタックス
新しい AOP ネーム空間のおかげで、Spring XML 構成によって AspectJ ポイントカット表現を指定できるようになり、ポイントカットでマッチングされたメソッドを使って任意の Spring Bean へのアドバイスを対象とすることができます。
前述の Person クラスについて考えてみましょう。このクラスには年齢プロパティだけでなく誕生日メソッドがあり、それによってそのプロパティを増加させています。
public void birthday() {
++age;
}
誕生日メソッドが呼び出された場合は、該当する人に必ずバースデーカードを送らなければならないといった要件があるとしましょう。クロスカッティングの要件として典型的な例があります。それは、私たちの基幹のビジネスロジックとは別の問題です。理想的には、Person オブジェクトに影響を与えずにその機能をモジュール化したいと考えています。
今度は、アドバイスについて考えてみましょう。もちろん、実際にバースデーカードを送ることだけでなく E カードを送ることがこのメソッドの主な役割となります。しかし、この記事の目的を考えると、私たちはバースデーカードを送る手順ではなく、トリガーインフラに興味があります。したがって、私たちは単にコンソール出力を利用します。このメソッドでは、今日が誕生日である Person へのアクセスが必要であり、誕生日メソッドが呼び出された場合は、必ず呼び出されるようにしたいと考えます。私たちのシンプルなアドバイス実装は以下のとおりです。
public class BirthdayCardSender {
public void onBirthday(Person person) {
System.out.println("I will send a birthday card to " +
person.getName() + "; he has just turned "
person.getAge());
}
}
基本的には、観察可能になる Person に変更を加えることなく、Person 上に Observer メカニズムのようなものを構築します。また、このケースでは、アスペクトとしてBirthdayCardSender オブジェクトを使いますが、フレームワークに固有のインターフェイスを実装する必要は一切ないことに注意してください。これによって、既存のクラスをアスペクトして使う可能性が実現し、Spring の非侵入的なプログラミングモデルをさらに拡張して、潜在的なアスペクトや正規のクラスを包括できるようになります。
Spring 2.0 では、以下のように BirthdayCardSender をアスペクトとして使うことができます。まず、BirthdayCardSender クラスを Bean として定義します。この作業は非常に簡単です。
<bean id="birthdayCardSenderAspect"
class="com.interface21.spring2.aop.BirthdayCardSender" />
私たちは、このオブジェクトを依存挿入したり、必要に応じて任意の Spring コンポーネントモデルサービスを適用したりすることができました。
次に、AOP 構成を追加して、Spring が管理する Person に対して birthday() メソッドが呼び出されたら、BirthdayCardSender Bean の onBirthday() メソッドを必ず呼び出すように Spring に指示をします。これは、新しい
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
次に、以下のように新しい AOP タグを使います。これは、このアスペクトを適用する完全な構成です。
<aop:config>
<aop:aspect id="sendBirthdayCard" ref="birthdayCardSenderAspect">
<aop:after
method="onBirthday"
pointcut="execution(void com.interface21..Person.birthday()) and
this(person)"
/>
</aop:aspect>
</aop:config>
ポイントカット表現が鍵です。ポイントカット表現についてもう少し詳しく見てみましょう。
execution(void com.interface21..Person.birthday()) and this(person)
接頭辞 execution() は、メソッドの実行の突き合わせを行っていることを表します。つまり、メソッドの動作を変更しているということです。
execution() 句の内容は、突き合わせ対象となるメソッドを定義します。ここで、さまざまなクラスに対して、一連のメソッドと一致した表現を記述することもできます(実際に、それがずっと一般的であり、より価値が高い使い方です。アドバイスがただ 1 つのメソッドと対応している場合は、それを具体化することは重要性が低いのです)。最後に、呼び出し中のオブジェクトを onBirthday() メソッドの引数にバインドします。これによってポイントカットが狭められ、Person オブジェクトに対する実行のみに対応できるようになり、ルックアップを一切必要とすることなく、呼び出されたオブジェクトに到達する簡潔な方法が実現しています。私たちは、引数のバインドを利用して、メソッドのパラメータをバインドしたり、必要に応じて型、例外、ターゲットを返したりすることができます。lookup コードの除去はなじみの深いものでしょう。これは、アドバイス対象の型への依存を効果的に挿入する方法です。Spring の考え方では、API を無くして、アドバイスメソッドにユニットテストを簡単に行えるようにします。
現在では、Spring のコンテキストで定義されているすべての Person は、ポイントカット表現が 1 つまたは複数のメソッドに対応していれば、自動的にプロキシ処理されます。このポイントカットに対する対応を有しているクラスを持つ Bean は影響を受けません。同様に、Spring コンテナによってインスタンス化されていないポイントカットによって突き合わせされたオブジェクトも影響を受けません。このアスペクト構成は、追加の XML ファイルにおいても、同一のファイルにおいても、上述の IoC の例で示した既存の Bean 構成に追加できるようになっています。念のため、Person は以下のように定義します。
<bean class="com.interface21.spring2.ioc.Person"
p:name="Tony"
p:age="53"
p:house-ref="number10"
/>
構成によって、Person やその他の Bean 定義をアドバイスに適切なものにする必要はありません。Spring コンテキストで構成された Person に対して birthday() メソッドを呼び出す場合、出力は以下のようになります。
I will send a birthday card to Tony; he has just turned 54
(訳:私はトニーにバースデーカードを送る。トニーは 54 歳になったばかりである。)
@AspectJ シンタックス
アドバイスは必ず Java メソッドに格納されます。しかし、ここまでは Spring XML で定義されたポイントカットを見てきました。
アドバイスがメソッドや Java 5 コメントのポイントカットに格納されている場合、AspectJ はアスペクトを定義する簡潔な方法を実現しています。ポイントカット表現言語は、AspectJ 独自のシンタックスにおける言語と同じであり、セマンティックスもまったく同じです。しかし、@AspectJ と呼ばれるこのスタイルでは、アスペクトは javac を使ってコンパイルされます。
@AspectJシンタックスでは、フレームワークに固有のコメントは、ビジネスロジックではなくアスペクトにあります。ビジネスロジックにコメントを導入してアスペクトを動かす必要はありません。
このコメントを主体としたプログラミングモデルは AspectWerkz が始めたものであり、2005 年の初めに AspectJ プロジェクトに統合されました。AspectJ 自体では、@AspectJ アスペクトは、ロード時のウィービングによって適用されます。クラスローダーフックによって、ロードされているクラスのバイトコードに変更が加えられ、必要に応じてアスペクトを適用します。AspectJ コンパイラもこのようなアスペクトを認識するので、実装戦略を選択することができます。
Spring 2.0 には @AspectJ 用のオプションが追加されています。Spring は独自のプロキシベースの AOP ランタイムを利用して、このようなアスペクトを Spring Bean に適用できます。
このスタイルを利用すると、前述の例において同じ機能をどのように実装できるのかを見てみましょう。
アスペクトクラスには、BirthdayCardSender クラスと同じアドバイスメソッドの本文が格納されていますが、それをアスペクトとして認識するために、org.aspectj.lang.annotation.Aspect のコメントが付けられています。同じパッケージのそれ以上のコメントによって、アドバイスメソッドが定義されます。
@Aspect
public class AnnotatedBirthdayCardSender {
@After("execution(void com.interface21..Person.birthday()) and this(person)")
public void onBirthday(Person person) {
System.out.println("I will send a birthday card to " +
person.getName() + "; he has just turned " +
person.getAge());
}
}
したがって、ポイントカットとアドバイスメソッドが結合され、アスペクトが完全なモジュールになります。すべての AspectJ アスペクトと同様、@AspectJ アスペクトには、任意の数のポイントカットとアドバイスメソッドを格納できます。
Spring XML では、これを再度 Bean と定義して、別のタグを追加することによってアスペクトが自動的に適用されるようにします。
<aop:aspectj-autoproxy />
<bean id="birthdayCardSenderAspect"
class="com.interface21.spring2.aop.AnnotatedBirthdayCardSender" />
aspectj-autoproxy タグは、@AspectJ アスペクトを自動的に認識するように Spring に指示し、そのポイントカットが対応する同じコンテキストのすべての Bean に適用するタグです。
XML スタイルと @AspectJ スタイルのどちらかを選択します
XML とコメントでは、どちらのほうがよい方法でしょうか。まず、コメントはコアビジネスロジックではなくアスペクトに格納されるので、切り替えコストは高くありません。通常は、Java コードのポイントカット表現をすべて具体化することに意味があるかどうかによって決定します。
自分のアスペクトがドメインに固有のものである場合は、コメントスタイルを検討するのがよいでしょう。つまり、ポイントカットとアドバイスは密接に関係しており、アドバイスは汎用的なものではなく、さまざまな状況で繰り返し使われる傾向があるということです。たとえば、バースデーカードの送信は、特定のアプリケーションクラス(Person)に固有のものであるため、コメントスタイルが適している用途の好例です。しかし、パフォーマンスをモニターするアスペクトは、異なるアプリケーションでは異なる使い方をされる可能性があり、アドバイスメソッドはポイントカットと分離して、ポイントカットが XML 構成の中でもっと自然に存在できるようにするのがいちばんよいのかもしれません。
以下の場合には XML を使います。
- Java 5 を使うことができないため選択の余地がない場合。Spring 2.0 の AOP 強化は、@AspectJ シンタックスの処理を除いては、Java 1.3 と 1.4、および Java 5 に有効ですが、コメントやその他の Java 5 構造物に対応したポイントカット表現を記述することはできない。
- 異なるコンテキストではアドバイスを使うことをお勧めする。
- 既存のコードをアドバイスとして使いたいが、AspectJ のコメントを導入したくない場合。たとえば、任意の POJO に対してメソッドを呼び出す Observer 動作の導入など。
プログラムの使い方
@AspectJ アスペクトを利用して、以下のようにプログラムで AOP プロキシを作成することも可能です。
Person tony = new Person();
tony.setName("Tony");
tony.setAge(53);
AspectJProxyFactory ajpf = new AspectJProxyFactory(tony);
ajpf.addAspect(new AnnotatedBirthdayCardSender());
Person proxy = ajpf.getProxy();
AnnotatedBirthdayCardSender は自動的に @AspectJ アスペクトとして認識され、プロキシが定義する動作によって、プロキシはターゲットを装飾します。このスタイルの場合、Spring IoC コンテナは必要とされません。
プログラムによるプロキシの作成は、インフラのコードやテストを記述する際には便利ですが、正規のビジネスアプリケーションではあまり使用されません。
AspectJ のポイントカットでできる高度な技
これまで見てきたことは、表面的なことに過ぎません。
AspectJ ポイントカット表現、Spring XML、@AspectJ スタイル (または、もちろん、AspectJ 言語そのもの)のもっと高度な機能のいくつかを見てみましょう。
- パラメータ、ターゲット、戻り値のバインド
- 型安全性から得られるメリット。メソッド署名の型はポイントカットで指定される。
- ポイントカット表現の合成による高度な表現の構築
- ポイントカット表現の再利用
ターゲットのバインドについてはすでに論じた。パラメータのバインドの例を挙げてみましょう。:
@Aspect
public class ParameterCaptureAspect {
@Before("execution(* *.*(String, ..)) && args(s)")
public void logStringArg(String s) {
System.out.println("String arg was '" + s + "'");
}
}
args() 句は、突き合わせが行なわれているメソッドの引数に対して拘束力があり、String タイプの最初の引数を有するメソッドに暗黙的に狭めらます。ポイントカットによって最初の引数(String 型の引数でなければならない)がバインドされるため、アドバイスメソッドではキャスティングは必要ありません。
safety 型は当然このメカニズムからの流れです。このポイントカットでターゲットとされたアドバイスは、間違った型の引数や対応しない引数によって不適切に呼び出されることは絶対にありません。
このメカニズムが優れていることの例を示すために、Spring 1.x MethodBeforeAdvice においてメカニズムがどのようになっているかを以下に示します。AOP Alliance MethodInterceptor(Spring 1.x アプリケーションに実装されている最も一般的なインターフェイス)の場合と同様、さまざまな引数を調べて、目的の引数を見つける必要があります。
public class ParameterCaptureInterceptor implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
if (args.length >= 1 && method.getParameterTypes()[0] == String.class) {
String s = (String) args[0];
System.out.println("String arg was '" + s + "'");
}
}
}
Spring AOP Pointcut を使って、MethodBeforeAdvice のガードを取り除くこともできますが、そのためにはJava コードを記述する必要があるでしょう。インターセプターにガードを設定するとポイントカットを利用するよりも遅くなります。それは、AOP ランタイムによって、絶対に呼び出されないアドバイスを最適化することができないからです。
ここでは、真の AOP をどの程度傍受から排除できますか、そして、それがよりシンプルかつパワフルな理由を確認できます。もう一度言いますが、EJB 3.0 の傍受は Spring の最初の AOP 機能に比べて格段に劣ります。これは、本当のポイントカットメカニズムが欠如しているからです。つまり、ClassCastException と ArrayIndexOutOfBoundsException のどちらもリスクになることが予想されるということです。EJB 3.0 では専用のアドバイス型が使えないため、Before アドバイスではなく Around アドバイス(インターセプター)を使う必要があるでしょう。また、InvocationContext オブジェクトを提供する必要があるため、アドバイスメソッドをユニットテストすることがより困難になります。
AspectJ ポイントカット表現言語の能力は、高度な構造物を構築できることだけではありません。エラーの可能性やアプリケーションの堅牢性を増すという点でも重要な役割を担っています。また、アドバイスメソッドにおいてガードコードの必要性をなくすことよって、必要なコードの量が大幅に減ります。
構成と再利用が本当の言語の特性です。AspectJ の主な目的は、ポイントカット表現においてこのような特性を実現することです。
実際のポイントカットの再利用について考えてみましょう。
@AspectJシンタックス(たとえば、Spring 2.0 AOP XML フォーマット)を使うと、名前付きのポイントカットを定義できます。@AspectJ シンタックスでは、無効なメソッドに対して @Pointcut コメントを以下のように使います。
@Pointcut("execution(public !void get*())")
public void getter() {}
@Pointcut コメントを使うとポイントカット表現を定義することができ、必要に応じて引数の数や型をポイントカットによってバインドすることができます。メソッドの名前は、ポイントカットの名前として使われます。
上述のポイントカットは、JavaBean のゲッターの突き合わせを行います。この突き合わせの強みについて注目してください。私たちは、単にワイルドカードではなく言語セマンティックスに取り組んでいます。稚拙な手法では、名前が get で始まるすべてのメソッドがゲッターとみなされます。このポイントカットは格段に堅牢です。ゲッターが公開されていて、無効でない戻り(!void)がなく、引数がない(引数の括弧が空であることでわかる)ことを示しています。
ポイントカットを追加して、int を返すメソッドの突き合わせを行ってみましょう。
@Pointcut("execution(public int *())")
public void methodReturningInt() {}
これで、これらのポイントカットの観点でアドバイスを表現できるようになりました。最初のサンプルでは、単に最初の名前付きポイントカット getter を参照するだけです。
@After("getter()")
public void getterCalled(JoinPoint jp) {
System.out.println("Method " + jp.getSignature().getName() +
" is a getter");
}
しかし、事態はこれからおもしろくなります。ここで、2 つのポイントカットを 1 つの表現に AND 処理することによって、int を返すゲッターにアドバイスを適用します。
@After("getter() and methodReturningInt()")
public void getterCalledThatReturnsInt(JoinPoint jp) {
System.out.println("ANDing of pointcuts: Method " +
jp.getSignature().getName() +
" is a getter that also returns int");
}
AND 処理とは、両方のポイントカットが適用されなければならないことを意味します。OR 処理とは、1 つまたはその他が適用されなければならないことを意味します。私たちは、必要とされるどのような複雑さの表現でも構築できます。
AND 処理と OR 処理の両方を、以下の完全なアスペクトで実証します。
@Aspect public class PointcutReuse {
@Pointcut("execution(public !void get*())" )
public void getter() {}
@Pointcut("execution(public int *())" )
public void methodReturningInt() {}
@Pointcut("execution(public void *(..))" )
public void voidMethod() {}
@Pointcut("execution(public * *())" )
public void takesNoArgs() {}
@After("methodReturningInt()" )
public void returnedInt(JoinPoint jp) {
System.out .println("Method " + jp.getSignature().getName() +
" returned int" );
}
@After("getter()")
public void getterCalled(JoinPoint jp) {
System.out .println("Method " + jp.getSignature().getName() +
" is a getter" );
}
@After("getter() and methodReturningInt()" )
public void getterCalledThatReturnsInt(JoinPoint jp) {
System.out.println("ANDing of pointcuts: Method " +
jp.getSignature().getName() +
" is a getter that also returns int");
}
@After("getter() or voidMethod()" )
public void getterOrVoidMethodCalled(JoinPoint jp) {
System.out .println("ORing of pointcuts: Method " +
jp.getSignature().getName() +
" is a getter OR is void" );
}
}
出力は以下のようになり、ポイントカット表現の OR 処理と AND 処理が示されます。
Method getName is a getter
ORing of pointcuts: Method getName is a getter OR is void
ORing of pointcuts: Method birthday is a getter OR is void
Method getName is a getter
ORing of pointcuts: Method getName is a getter OR is void
Method getAge returned int
Method getAge is a getter
ANDing of pointcuts: Method getAge is a getter that also returns int
ORing of pointcuts: Method getAge is a getter OR is void
I will send a birthday card to Tony; he has just turned 54
ポイントカット構成は Spring AOP XML でも実現できます。その場合は、演算子 "&&" と "||" の代わりに and と or を使って、XML 属性値の問題を回避します。
パワーユーザーは、AspectJ ライブラリアスペクトを再利用
AspectJ 言語そのもので記述された AspectJ ポイントカット表現を再利用して、JAR ファイルにコンパイルすることができます。Eclipse を使うと、AJDT プラグインを使ってこのようなアスペクトを作成することができます。または、すでに AspectJ を使っている場合は、すでにこのようなアスペクトが存在している可能性があるので、そのアスペクトを使うことをお勧めします。
証明として、前述の例の一部を書き直し、AspectJ アスペクトにポイントカットを挿入してみます。
public aspect LibraryAspect {このアスペクトは Aspect 言語で記述されているため、AspectJ ajc コンパイラでコンパイルする必要があります。アスペクトとポイントカット用のキーボードを使えることに注意してください。
pointcut getter() :
execution(public !void get*());
...
}
Spring で使用する @AspectJ アスペクトのこのアスペクトは以下のように参照できます。通常はクラスパス上の JAR ファイルにパッケージされたアスペクトの FQN を使うことができることに注意してください。
@Aspect
public class PointcutReuse {
@After("mycompany.mypackage.LibraryAspect.getter()")
public void getterCalled(JoinPoint jp) {
System.out.println("Method " + jp.getSignature().getName() +
" is a getter");
}
また、このクラスは javac を使ってコンパイルし、Spring で適用することができます。
Spring XML でも AspectJ アスペクトは参照できます。ご存知のとおり、Spring 2.0 と AspectJ は非常に密接に統合されていますが、Spring AOP では、AspectJ コンパイラやウィーバを使わなくても完全なランタイムを実現しています。
非常に複雑なポイントカット表現がある場合は、AspectJ 言語とツールサポートが必須の場合と同様、AspectJ ライブラリアスペクトを使うのがベストです。
ベストプラクティス
Spring ユーザーにとってはどのような意味があるのでしょうか?
AOP によって企業ソフトウェアの重要な問題が解決できることを認めてもらい、傍受や AOP の代替品と比べて、AspectJ プログラミングモデルがいかにパワフルで簡潔かを理解頂きたいのです。
既存の Spring MethodInterceptor やその他のアドバイス実装を新しいプログラミングモデルに移行する必要はありません。そのままでも十分に機能します。しかし、今後は新しいプログラミングモデルを使うことが好ましく、格段に強制的になります。
ProxyFactoryBean または TransactionProxyFactoryBean を使って同時にプロキシを構成している場合は、自動プロキシ処理(Spring 2.0 では、より簡単かつより当たり前になる)によって Spring 構成のボリュームが格段に小さくなり、チームのメンバー間で作業を分割がスムーズになります。
ランタイムはどのようになるのでしょうか
用法モデルは異なるように見えますが、概念(ジョインポイント、ポイントカット、アドバイス)は、Spring AOP や Spring で 2003 年以来実装されてきた AOP Alliance API の概念とまったく同じであることを記憶に留めておいてください。Spring AOP には常にこのような概念に対応したインターフェイスが備わっています。
おそらくもっと驚かれるであろうことは、実装は実際にはカバーの下では、まったく同じであるということです。AspectJ 構造物を活用する新しいプログラミングモデルは、既存の Spring AOP ランタイムを基盤に構築されています。Spring AOP は常に高い柔軟性を有していたため、このことで大きな変化は生じません(Spring 1.x と同様、org.springframework.aop.framework.Advised インターフェイスを使って、今でも AOP プロキシの状態を問い合わせたり、変更を加えたりすることができます)。
つまり、AOP Alliance と Spring AOP アスペクトを AspectJ スタイルのアスペクトと突き合わせして、それらを組み合わせることができるということです。これは、既存のアスペクトを活用したい場合は、特に重要です。
プログラムによるプロキシの作成の例を増やして、これを証明してみましょう。従来の Spring AOP スタイル MethodInterceptor を追加します。
Person tony = new Person();
tony.setName("Tony");
tony.setAge(53);
AspectJProxyFactory ajpf = new AspectJProxyFactory(tony);
ajpf.addAspect(new AnnotatedBirthdayCardSender());
Person proxy = ajpf.getProxy();
ajpf.addAdvice(new MethodInterceptor() {
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("MethodInterceptor: Call to " + mi.getMethod());
return mi.proceed();
}
});
出力は以下のようになり、MethodInterceptor(ポイントカットがなければ、すべてのメソッドの呼び出しに対応する)からの出力が @AspectJ スタイルで記述された BirthdayCardSender の出力に挿入されます。
MethodInterceptor: Call to public void
com.interface21.spring2.ioc.Person.birthday()
MethodInterceptor: Call to public java.lang.String
com.interface21.spring2.ioc.Person.getName()
MethodInterceptor: Call to public int
com.interface21.spring2.ioc.Person.getAge()
I will send a birthday card to Tony; he has just turned 54
AOP の統一に向けて
Spring 2.0 によって、AOP の世界に新しくて望ましい統一が実現します。アスペクトの実装が運用モデルから初めて独立するようになりました。これまで見てきた @AspectJ の例のすべてが AspectJ コンパイラを使ってコンパイルしたり、AspectJ ロードタイムウィービングを使って適用したりすることができるだけでなく、Spring によって適用されるようになります。Spring の考え方では、さまざまなランタイム状況をカバーするプログラミングモデルを 1 つ有しています。
そして、AspectJ 自体を採用したい場合は、AspectJ ポイントカット表現の概念を幅広い層にもたらす Spring によって、自然な進展が起こります。
いつ AspectJ を使うべきなのか。以下にガイドラインを示します。
- Spring コンテナでインスタンス化できないきめ細かいオブジェクトをアドバイスしたいとき。
- フィールドアクセスやオブジェクト構築など、パブリックメソッドの実行以外のジョインポイントをアドバイスしたいとき。
- 透過的な方法で自己呼び出しをアドバイスしたいとき。
- アドバイスする必要があるオブジェクトが非常に多くの回数呼び出され、プロキシパフォーマンスのオーバーヘッドが許容されないとき(このベースで決定する前にベンチマークを行うように注意が必要。Spring AOP のプロキシ処理のオーバーヘッドは、通常の使用ではほとんど検出不可能である)。
- AspectJ の機能を利用して、コンパイラによってフラグが付けられる警告やエラーを宣言したい場合。これは、アーキテクチャを強制する場合に特に便利である。
必ずしも、「どちらか一方」の選択ではありません。AspectJ と Spring AOP を同時に使うことは可能です。両者は競合しないのです。
Java 5
Spring 2.0 は、Java 1.3 や 1.4 との下位互換性を保っています。しかし、Java 5 をターゲットにしている新しい機能の数は増えています。
タイプインターフェイスなど、そのような新機能の中には、すでに説明したように、無料のものもあります。また、自分で選択しなければならないものもあります。いくつかを簡単に見てみましょう。
新しい API
さまざまな新しい API が、Java の初期バージョンで動作し続ける中核機能に基づいて、Java 5 の機能を備えています。
特に以下の機能が挙げられます。
- SimpleJdbcTemplate: なじみの深い JdbcTemplate とともに新しいクラス。これによって、JDBC の使い方がいっそう簡単になる。
- AspectJProxyFactory: @AspectJ アスペクトを使ってプロキシをプログラムで作成できるようになっているProxyFactory とともに新しいクラス。
このようなクラスの数は、次第に増えていくでしょう。
SimpleJdbcTemplate が例証となります。集約関数の呼び出しに対する影響について見てみましょう。Java 5 よりも前の JdbcTemplate を使って、以下のようにバインド変数をアレイにラップする必要があります。
jdbcTemplate.queryForInt("SELECT COUNT(0) FROM T_CLIENT WHERE TYPE=? AND CURRENCY=?",
new Object[] { new Integer(13), "GBP" }
);
Java 5 を使えば、基本ラッパー型が必要なくなるので、オートボックス処理によって多少のノイズを取り除くことができます。これは、Spring が新しいものを提供しなければならないことではなく、単に言語の特徴に起因しています。
jdbcTemplate.queryForInt("SELECT COUNT(0) FROM T_CLIENT WHERE TYPE=? AND CURRENCY=?",
new Object[] { 13, "GBP" }
);
しかし、Java 5 を採用することで、オブジェクトアレイ全体の必要性をなくすことができます。以下の例は、SimpleJdbcTemplate がバインド変数の varargs をどのように使うかを示しています。つまり、開発担当者は、アレイなしで任意の数の varargs を提供できるということです。
simpleJdbcTemplate.queryForInt("SELECT COUNT(0) FROM T_CLIENT WHERE TYPE=? AND CURRENCY=?",
13, "GBP"
);
付加的なメリットとして、バインド変数があるケースとないケースを区別する必要がなくなったということが挙げられます。そのためには、JdbcTemplate に対して 2 つのメソッドが必要になったが、リテラル SQL を取るオーバーロードされたメソッドに、空の Object アレイを渡す必要性をなくすために、SimpleJdbcTemplate を使って、フレームワークコードが varargs の長さをチェックすることができるようになりました。次の例は同じメソッドを呼び出しています。
simpleJdbcTemplate.queryForInt("SELECT COUNT(0) FROM T_CLIENT");
もっと重要なメリットもあります。ジェネリクスによって、署名が明確になり、キャストがなくなります。たとえば、JdbcTemplate に対するqueryForMap() メソッドは、ResultSet のカラム名からカラム値に Map を返す。SimpleJdbcTemplate に対するメソッドの署名が明示的な部分であれば、これはもっと明確になります。
public Map<String, Object> queryForMap(String sql, Object... args)
throws DataAccessException
このようなマップのリストを返すメソッドで、これはさらに明確になります。
public List<Map<String, Object>> queryForList(String sql, Object ... args)
throws DataAccessException
SimpleJdbcTemplate の付加的な目的は、もっとも頻繁に使われるようなメソッドのみを提供することです。JdbcTemplate は、Spring で最も大きなクラスの 1 つであり、多くのメソッドを有しています。これらのメソッドの一部には深遠な目的がある。このような高度なケースでは、総合的な問題の複雑さという観点で、言語シンタックスシュガーの重要度は低くなりがちです。このような場合は、getJdbcOperations() メソッドでアクセスできる JdbcTemplate インスタンスを SimpleJdbcTemplate がラップします。
DAO サポートクラスの使い方モデルをサポートするために、Spring 2.0 には SimpleJdbcDaoSupport クラスが備わっており、これによって JdbcTemplate を事前構成しています。また、SimpleJdbcTemplate をインスタンス化したり、直接挿入したりするのは JdbcTemplate と同じくらい簡単であり、単にリレーショナルデータアクセスに対する Spring のすべてのサポートの開始点である javax.sql.Datasource の実装を実現しているだけです。JdbcTemplate と同様、SimpleJdbcTemplate は Spring を別の方法で使うことなくクラスライブラリとして使えます。
アノテーション
Spring 1.2 で、まずアノテーションをオプションの機能として導入し、徐々に拡張してきました。
AspectJ から AOP のアノテーションを利用することについてはすでに述べました。これらは、シングルコードモジュールで、ポイントカットやアドバイスを簡潔に表現する方法を実現しています。
Spring には、トランザクション管理といった分野の数多くの独自のコメントも備えています。1.2 では以下が導入されました。
- @Transactional:クラス、インターフェイス、メソッドを処理可能としてマークする。
- org.springframework.jmx.export.annotation パッケージの @ManagedResource など、さまざまなコメント、確認操作、JMX 管理のためにエクスポートする属性とオブジェクト。
以下に、2.0 の最も重要な新しいアノテーションを示します。
- @Configurable: Spring によってインスタンス化されていない特定のオブジェクトでも、Spring のアフター構築を使って依存挿入する必要があることを示す。AspectJ DI アスペクトをドライブする。この記事のパート 2 で説明。
- @Required: 必要とされる JavaBean セッターメソッドを示す。この強制をアクティブにするには、アプリケーションコンテキストで RequiredAnnotationBeanPostProcessor を定義する。独自の非侵入プログラミングモデルと既存のクラスを処理する能力を有する Spring の考え方では、Spring はほかのコメントの利用を強制し、RequiredAnnotationBeanPostProcessor の構成によって、必要とされるプロパティを示す。
- @Repository: DAO オブジェクトを、(Domain Driven Design 用語の)Repository パターンの表現として識別する。Spring 2.0 では、@Repository でコメントが付けられたオブジェクトのテクノロジー別の例外を、Spring の汎用 DataAccessException 階層に自動的に変換することができるアスペクトが実現されている。
新しい AbstractJpaTest や汎用スーパークラス AbstractAnnotationAwareTransactionalTests といった Spring の JPA テスト用の統合テストスーパークラスは、@Repeat(テストを繰り返す)や @ExpectedException(テストは特定の例外を投じる必要があり、そうしなければ失敗することを示す)といったアノテーションをサポートするようになりました。残念ながら、JUnit 3 は具体的な継承に基づいて設計されているため、このような便利なアノテーションは、Spring を使ったほかのテストでは利用できません。JUnit 4 の巻き取りが増えるにつれて、この機能をほかのユーザーに開放することができる統合テストバージョンを提供します。
自分のアノテーションを解釈したい場合はどうすればよいのでしょうか。このようなケースでは、もちろん Spring のさまざまな拡張フックが役に立ちます。たとえば、RequiredAnnotationBeanPostProcessor 作業と同じ方法で、特定のアノテーションでメソッドを識別する BeanPostProcessor を記述することもできます。近日発売予定の WebLogic 10 で使われている Pitchfork プロジェクト(http://www.interface21.com/pitchfork)では、これらの拡張ポイントを利用して、Spring の上に JSR-250 アノテーションと EJB 3.0 インターセプトアノテーションを実装しています。
また、Spring 2.0 に備わった AspectJ ポイントカット表現言語には、きわめて強力なアノテーション突き合わせ機能が備わっていることは注目に値します。アノテーションをターゲットとしたポイントカットは簡単に記述できます。たとえば、以下のポイントカット表現は、Spring フレームワークパッケージのアノテーションが付けられたすべてのメソッドの突き合わせを行います。
execution(@(org.springframework..*) * *(..))
以下の表現は、@Transactional アノテーションが付けられたすべてのクラスの突き合わせを行います。
@within(org.springframework.transaction.annotation.Transactional)
AspectJ 5 は AspectJ 言語を大きく拡張したものであり、基盤となる言語の進化に遅れないようにするための技術的なすばらしい取り組みでした。また、ポイントカット表現も、ジェネリクスや varargs といったその他の Java 5 構造物の突き合わせも行うことができます。
詳細
このシリーズの次の記事では、以下の項目を取り上げます。
- 動的な言語のサポート。Spring コンポーネントモデルが、Spring 2.0 でどのようにクロス言語になるか。
- メッセージ送信と非同期呼び出しのサポート
- データアクセスと新しい Java Persistence API(JPA)との統合
- 使い勝手を中心とした Spring MVC の強化
- 新しい Spring Portlet MVC フレームワーク
- ドメインオブジェクトの依存挿入の新しい可能性
ところで、今日私が説明した内容をもっと詳しく知りたい方には、以下のリソースをお勧めします。
- Spring リファレンスマニュアル。これまでもすばらしい内容でしたが、Spring 2.0 ではさらによくなっています。 http://static.springframework.org/spring/docs/2.0.x/reference/index.html(英語)
- AspectJ の能力をよく理解するには、AspectJ に関する優れた書籍がいくつかあります。『AspectJ in Action』(Ramnivas Laddad 著、Manning、2003)、『Eclipse AspectJ』(Adrian Colyer、Andy Clement、George Harley、Matthew Webster 著、Addison-Wesley、2005)をお勧めします。
- AspectJ の変更点を理解するには、さまざまな書籍が出版される予定ですが、現時点では、AspectJ Development Kit Developer's Notebook の特に「An Annotation Based Development Style」の章が非常に役に立ちます。http://www.eclipse.org/aspectj/doc/released/adk15notebook/index.html (英語)を参照。
この記事に付属するサンプルコードは、ここ(zip)からダウンロードできます。
原文はこちらです:http://www.infoq.com/articles/spring-2-intro
(このArticleは2007年1月15日にリリースされました)